package tw.com.hitevision.whiteboard.android.widgets.floatView.widget.activityfloat

import android.animation.Animator
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
import android.util.Log
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import tw.com.hitevision.whiteboard.android.Props
import tw.com.hitevision.whiteboard.android.widgets.floatView.anim.AnimatorManager
import tw.com.hitevision.whiteboard.android.widgets.floatView.data.FloatConfig
import tw.com.hitevision.whiteboard.android.widgets.floatView.enums.SidePattern
import tw.com.hitevision.whiteboard.android.widgets.floatView.utils.Logger
import kotlin.math.min

abstract class AbstractDragFloatingView(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    abstract fun getLayoutId(): Int?

    abstract fun renderView(view: View)

    var config: FloatConfig

    private var parentHeight = 0
    private var parentWidth = 0

    private var lastX = 0
    private var lastY = 0

    private var leftDistance = 0
    private var rightDistance = 0
    private var topDistance = 0
    private var bottomDistance = 0
    private var minX = 0
    private var minY = 0
    private var parentRect = Rect()
    private var floatRect = Rect()
    private var parentView: ViewGroup? = null
    private var isCreated = false

    init {
        FrameLayout(context, attrs, defStyleAttr)
        config = FloatConfig()
        initView(context)
        setOnClickListener { }
    }

    protected fun initView(context: Context) {
        Log.d("yqcc", "initView2: ")
        if (getLayoutId() != null) {
            val view: View = LayoutInflater.from(context).inflate(getLayoutId()!!, this)
            if(Props.isFake4k()) { changeDPI(3f) }
            this.renderView(view)
            config.invokeView?.invoke(this)
            Log.d("yqcc", "initView222: ")
        }
    }

    @SuppressLint("DrawAllocation")
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        if (!isCreated) {
            isCreated = true
            if (config.locationPair != Pair(0, 0)) {
                x = config.locationPair.first.toFloat()
                y = config.locationPair.second.toFloat()
            } else {
                x += config.offsetPair.first
                y += config.offsetPair.second
            }

            initParent()
            initDistanceValue()
            enterAnim()
        }
    }

    private fun initParent() {
        if (parent != null && parent is ViewGroup) {
            parentView = parent as ViewGroup
            parentHeight = parentView!!.height
            parentWidth = parentView!!.width
            parentView!!.getGlobalVisibleRect(parentRect)
            Logger.e("parentRect: $parentRect")
        }
    }

    override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
        if (event != null) updateView(event)
        return config.isDrag || super.onInterceptTouchEvent(event)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {

        if (event != null) updateView(event)

        return config.isDrag || super.onTouchEvent(event)
    }

    private fun updateView(event: MotionEvent) {
        config.callbacks?.touchEvent(this, event)
        config.floatCallbacks?.builder?.touchEvent?.invoke(this, event)

        if (!config.dragEnable || config.isAnim) {
            config.isDrag = false
            isPressed = true
            return
        }

        val rawX = event.rawX.toInt()
        val rawY = event.rawY.toInt()
        when (event.action and MotionEvent.ACTION_MASK) {
            MotionEvent.ACTION_DOWN -> {

                config.isDrag = false
                isPressed = true
                lastX = rawX
                lastY = rawY
                // parent dis allow intercept touchEvent
                parent.requestDisallowInterceptTouchEvent(true)
                initParent()
            }

            MotionEvent.ACTION_MOVE -> {
                // only parent drag
                if (parentHeight <= 0 || parentWidth <= 0) return

                val dx = rawX - lastX
                val dy = rawY - lastY

                if (!config.isDrag && dx * dx + dy * dy < 81) return
                config.isDrag = true

                var tempX = x + dx
                var tempY = y + dy

                tempX = when {
                    tempX < 0 -> 0f
                    tempX > parentWidth - width -> parentWidth - width.toFloat()
                    else -> tempX
                }
                tempY = when {
                    tempY < 0 -> 0f
                    tempY > parentHeight - height -> parentHeight - height.toFloat()
                    else -> tempY
                }

                when (config.sidePattern) {
                    SidePattern.LEFT -> tempX = 0f
                    SidePattern.RIGHT -> tempX = parentRect.right - width.toFloat()
                    SidePattern.TOP -> tempY = 0f
                    SidePattern.BOTTOM -> tempY = parentRect.bottom - height.toFloat()

                    SidePattern.AUTO_HORIZONTAL ->
                        tempX = if (rawX * 2 - parentRect.left > parentRect.right)
                            parentRect.right - width.toFloat() else 0f

                    SidePattern.AUTO_VERTICAL ->
                        tempY = if (rawY - parentRect.top > parentRect.bottom - rawY)
                            parentRect.bottom - height.toFloat() else 0f

                    SidePattern.AUTO_SIDE -> {
                        leftDistance = rawX - parentRect.left
                        rightDistance = parentRect.right - rawX
                        topDistance = rawY - parentRect.top
                        bottomDistance = parentRect.bottom - rawY

                        minX = min(leftDistance, rightDistance)
                        minY = min(topDistance, bottomDistance)

                        val pair = sideForLatest(tempX, tempY)
                        tempX = pair.first
                        tempY = pair.second
                    }
                    else -> Unit
                }

                x = tempX
                y = tempY
                config.anchorPair = Pair(x.toInt(), y.toInt())
                lastX = rawX
                lastY = rawY
                config.callbacks?.drag(this, event)
                config.floatCallbacks?.builder?.drag?.invoke(this, event)
            }

            MotionEvent.ACTION_UP -> {

                isPressed = !config.isDrag

                if (config.isDrag) {
                    when (config.sidePattern) {
                        SidePattern.RESULT_LEFT,
                        SidePattern.RESULT_RIGHT,
                        SidePattern.RESULT_TOP,
                        SidePattern.RESULT_BOTTOM,
                        SidePattern.RESULT_HORIZONTAL,
                        SidePattern.RESULT_VERTICAL,
                        SidePattern.RESULT_SIDE -> sideAnim()
                        else -> touchOver()
                    }
                }
            }

            else -> return
        }
    }

    /**
     *  drag or adsorption over and update
     */
    private fun touchOver() {
        config.isAnim = false
        config.isDrag = false
        config.callbacks?.dragEnd(this)
        config.floatCallbacks?.builder?.dragEnd?.invoke(this)
    }

    /**
     *  drag over and absorb parentView edge
     */
    private fun sideAnim() {
        initDistanceValue()
        var animType = "translationX"
        var startValue = 0f
        val endValue: Float = when (config.sidePattern) {
            SidePattern.RESULT_LEFT -> {
                animType = "translationX"
                startValue = translationX
                -leftDistance + translationX
            }
            SidePattern.RESULT_RIGHT -> {
                animType = "translationX"
                startValue = translationX
                rightDistance + translationX
            }
            SidePattern.RESULT_HORIZONTAL -> {
                animType = "translationX"
                startValue = translationX
                if (leftDistance < rightDistance) -leftDistance + translationX else rightDistance + translationX
            }

            SidePattern.RESULT_TOP -> {
                animType = "translationY"
                startValue = translationY
                -topDistance + translationY
            }
            SidePattern.RESULT_BOTTOM -> {
                animType = "translationY"
                startValue = translationY
                bottomDistance + translationY
            }
            SidePattern.RESULT_VERTICAL -> {
                animType = "translationY"
                startValue = translationY
                if (topDistance < bottomDistance) -topDistance + translationY else bottomDistance + translationY
            }

            SidePattern.RESULT_SIDE -> {
                if (minX < minY) {
                    animType = "translationX"
                    startValue = translationX
                    if (leftDistance < rightDistance) -leftDistance + translationX else rightDistance + translationX
                } else {
                    animType = "translationY"
                    startValue = translationY
                    if (topDistance < bottomDistance) -topDistance + translationY else bottomDistance + translationY
                }
            }
            else -> 0f
        }

        val animator = ObjectAnimator.ofFloat(this, animType, startValue, endValue)
        animator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {}

            override fun onAnimationEnd(animation: Animator?) {
                touchOver()
            }

            override fun onAnimationCancel(animation: Animator?) {}

            override fun onAnimationStart(animation: Animator?) {
                config.isAnim = true
            }
        })
        animator.start()
    }

    /**
     *  absorb recent parentView edge
     */
    private fun sideForLatest(x: Float, y: Float): Pair<Float, Float> {
        var x1 = x
        var y1 = y
        if (minX < minY) {
            x1 = if (leftDistance == minX) 0f else parentWidth - width.toFloat()
        } else {
            y1 = if (topDistance == minY) 0f else parentHeight - height.toFloat()
        }
        return Pair(x1, y1)
    }

    /**
     *  calculate edge data
     */
    private fun initDistanceValue() {

        getGlobalVisibleRect(floatRect)

        leftDistance = floatRect.left - parentRect.left
        rightDistance = parentRect.right - floatRect.right
        topDistance = floatRect.top - parentRect.top
        bottomDistance = parentRect.bottom - floatRect.bottom

        minX = min(leftDistance, rightDistance)
        minY = min(topDistance, bottomDistance)
        Logger.i("$leftDistance   $rightDistance   $topDistance   $bottomDistance")
    }

    /**
     *  enter animation
     */
    private fun enterAnim() {
        if (parentView == null) return
        val manager: AnimatorManager? =
            AnimatorManager(config.floatAnimator, this, parentView!!, config.sidePattern)
        val animator: Animator? = manager?.enterAnim()
        animator?.addListener(object : Animator.AnimatorListener {
            override fun onAnimationRepeat(animation: Animator?) {}

            override fun onAnimationEnd(animation: Animator?) {
                config.isAnim = false
            }

            override fun onAnimationCancel(animation: Animator?) {}

            override fun onAnimationStart(animation: Animator?) {
                config.isAnim = true
            }
        })
//        animator?.start()
    }

    /**
     *  exit animation
     */
    internal fun exitAnim() {
        if (config.isAnim || parentView == null) return
        parentView?.removeView(this)
//        val manager: AnimatorManager? =
//            AnimatorManager(config.floatAnimator, this, parentView!!, config.sidePattern)
//        val animator: Animator? = manager?.exitAnim()
//        if (animator == null) parentView?.removeView(this)
//        else {
//            animator.addListener(object : Animator.AnimatorListener {
//                override fun onAnimationRepeat(animation: Animator?) {}
//
//                override fun onAnimationEnd(animation: Animator?) {
//                    config.isAnim = false
//                    parentView?.removeView(this@AbstractDragFloatingView)
//                }
//
//                override fun onAnimationCancel(animation: Animator?) {}
//
//                override fun onAnimationStart(animation: Animator?) {
//                    config.isAnim = true
//                }
//            })
//            animator.start()
//        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        config.callbacks?.dismiss()
        config.floatCallbacks?.builder?.dismiss?.invoke()
    }

    private fun changeDPI(newDpi: Float) {
        val metrics = resources.displayMetrics
        if (newDpi != metrics.density) {
            val scale = metrics.density / newDpi
            metrics.density /= scale
            metrics.scaledDensity /= scale
            metrics.densityDpi = (metrics.densityDpi / scale).toInt()
            metrics.xdpi /= scale
            metrics.ydpi /= scale
            resources.displayMetrics.setTo(metrics)
        }
    }

}